package org.finesys.common.xss.filter;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HTMLFilter;
import org.springframework.http.MediaType;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.*;

public class XssRequestWrapper extends HttpServletRequestWrapper {

    private static final ThreadLocal<HTMLFilter> FILTER = ThreadLocal.withInitial(() -> {
        Map<String, Object> map = new HashMap<>();
        Map<String, List<String>> vAllowedMap = new HashMap<>();
        //a标签
        List<String> a_attrs = new ArrayList<>();
        a_attrs.add("href");
        a_attrs.add("target");
        vAllowedMap.put("a", a_attrs);
        //img标签
        List<String> img_attrs = new ArrayList<>();
        img_attrs.add("src");
        img_attrs.add("width");
        img_attrs.add("height");
        img_attrs.add("alt");
        vAllowedMap.put("img", img_attrs);
        //
        List<String> no_attrs = new ArrayList<>();
        vAllowedMap.put("b", no_attrs);
        vAllowedMap.put("strong", no_attrs);
        vAllowedMap.put("i", no_attrs);
        vAllowedMap.put("em", no_attrs);
        //
        String[] vSelfClosingTags = new String[]{"img"};
        String[] vNeedClosingTags = new String[]{"a", "b", "strong", "i", "em"};
        String[] vDisallowed = new String[]{};
        String[] vAllowedProtocols = new String[]{"http", "mailto", "https"};
        String[] vProtocolAttrs = new String[]{"src", "href"};
        String[] vRemoveBlanks = new String[]{"a", "b", "strong", "i", "em"};
        String[] vAllowedEntities = new String[]{"amp", "gt", "lt", "quot"};
        boolean stripComment = true;
        boolean encodeQuotes = false;
        boolean alwaysMakeTags = true;
        map.put("vAllowed", vAllowedMap);
        map.put("vSelfClosingTags", vSelfClosingTags);
        map.put("vNeedClosingTags", vNeedClosingTags);
        map.put("vDisallowed", vDisallowed);
        map.put("vAllowedProtocols", vAllowedProtocols);
        map.put("vProtocolAtts", vProtocolAttrs);
        map.put("vRemoveBlanks", vRemoveBlanks);
        map.put("vAllowedEntities", vAllowedEntities);
        map.put("stripComment", stripComment);
        map.put("encodeQuotes", encodeQuotes);
        map.put("alwaysMakeTags", alwaysMakeTags);
        return new HTMLFilter(map);
    });

    public XssRequestWrapper(HttpServletRequest request) {
        super(request);
    }

    private static String filter(String val) {
        if (!StringUtils.hasText(val)) {
            return val;
        }
        return FILTER.get().filter(val);
    }

    @Override
    public String getParameter(String name) {
        return filter(super.getParameter(name));
    }

    @Override
    public String[] getParameterValues(String name) {
        String[] values = super.getParameterValues(name);
        if (ArrayUtil.isEmpty(values)) {
            return values;
        }
        return Arrays.stream(values).map(XssRequestWrapper::filter).toArray(String[]::new);
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        Map<String, String[]> parameterMap = super.getParameterMap();
        if (ObjectUtils.isEmpty(parameterMap)) {
            return parameterMap;
        }
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            String[] values = entry.getValue();
            for (int i = 0; i < values.length; i++) {
                values[i] = filter(values[i]);
            }
        }
        return parameterMap;
    }

    @Override
    public String getHeader(String name) {
        return filter(super.getHeader(name));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        boolean json = StrUtil.startWith(this.getContentType(), MediaType.APPLICATION_JSON_VALUE);
        if (!json) {
            return super.getInputStream();
        }
        String content = IoUtil.readUtf8(super.getInputStream());
        content = filter(content);
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(content.getBytes());
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return true;
            }

            @Override
            public boolean isReady() {
                return true;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }

            @Override
            public int read() throws IOException {
                return byteArrayInputStream.read();
            }
        };
    }
}
